﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;

namespace Dycox.Utils
{
    public static class ConvertUtil
    {
        public static readonly System.Globalization.CultureInfo DefaultCulture = System.Globalization.CultureInfo.CurrentCulture;

        public static object ConvertType(object value, Type targetType)
        {
            return ConvertType(value, targetType, null, null);
        }

        public static object ConvertType(object value, ITypeDescriptorContext context)
        {
            return ConvertType(value, context, null);
        }

        public static object ConvertType(object value, ITypeDescriptorContext context, System.Globalization.CultureInfo culture)
        {
            if (context == null)
                throw new ArgumentNullException("context");
            if (context.PropertyDescriptor == null)
                throw new ArgumentException("PropertyDescriptor is null.", "context");

            return ConvertType(value, context.PropertyDescriptor.PropertyType, context, culture);
        }

        private static object ConvertType(object value, Type targetType, ITypeDescriptorContext context, System.Globalization.CultureInfo culture)
        {
            if (value == null)
                throw new ArgumentNullException("value");
            if (targetType == null)
                throw new ArgumentNullException("targetType");

            object v = value;
            Exception error;

            if (TryConvertType(ref v, targetType, context, culture, out error))
                return v;
            
            throw new InvalidCastException("Convert from " + value.GetType().FullName + " to " + targetType.FullName + " failed.", error);
        }

        public static T ConvertType<T>(object value)
        {
            return ConvertType<T>(value, null, null);
        }

        public static T ConvertType<T>(object value, ITypeDescriptorContext context, System.Globalization.CultureInfo culture)
        {
            return (T)ConvertType(value, typeof(T), context, culture);
        }

        public static bool TryConvertType(ref object value, Type targetType)
        {
            return TryConvertType(ref value, targetType, null, null);
        }

        public static bool TryConvertType(ref object value, Type targetType, ITypeDescriptorContext context, System.Globalization.CultureInfo culture)
        {
            Exception error;

            return TryConvertType(ref value, targetType, context, culture, out error);
        }

        private static bool TryConvertType(ref object value, Type targetType, ITypeDescriptorContext context, System.Globalization.CultureInfo culture, out Exception error)
        {
            error = null;

            if (value != null && targetType != null)
            {
                Type type = value.GetType();
                if (culture == null) culture = DefaultCulture;

                if (targetType == type || targetType.IsAssignableFrom(type))
                    return true;

                TypeConverter converter;

                if (context != null && context.PropertyDescriptor != null)
                    converter = context.PropertyDescriptor.Converter;
                else
                    converter = TypeDescriptor.GetConverter(targetType);

                if (converter.CanConvertFrom(type))
                {
                    try
                    {
                        value = converter.ConvertFrom(context, culture, value);
                        return true;
                    }
                    catch(Exception ex)
                    {
                        error = ex;
                    }
                }

                if (value is IConvertible && targetType.IsPrimitive)
                {
                    try
                    {
                        value = Convert.ChangeType(value, targetType, culture);
                        return true;
                    }
                    catch (Exception ex)
                    {
                        error = ex;
                    }
                }

                converter = TypeDescriptor.GetConverter(type);
                if (converter.CanConvertTo(targetType))
                {
                    try
                    {
                        value = converter.ConvertTo(context, culture, value, targetType);
                        return true;
                    }
                    catch(Exception ex)
                    {
                        error = ex;
                    }
                }

                if (value is string)
                {
                    string name = (string)value;
                    MemberInfo[] members = targetType.GetMember(name, MemberTypes.Property | MemberTypes.Method, BindingFlags.Static | BindingFlags.Public);
                    if (members.Length > 0)
                    {
                        MemberInfo m = members[0];
                        if (m.MemberType == MemberTypes.Property)
                        {
                            PropertyInfo p = (PropertyInfo)m;
                            if (p.PropertyType == targetType)
                            {
                                value = p.GetValue(null, null);
                                return true;
                            }
                        }
                        if (m.MemberType == MemberTypes.Method)
                        {
                            MethodInfo method = (MethodInfo)m;
                            if (method.ReturnType == targetType && method.GetParameters().Length == 0)
                            {
                                value = method.Invoke(null, null);
                                return true;
                            }
                        }
                    }
                }
            }

            return false;
        }

        public static PropertyDescriptor CreatePropertyDescriptor(this PropertyInfo info, bool attrInherit)
        {
            if (info == null)
                throw new ArgumentNullException("info");

            return TypeDescriptor.CreateProperty(
                info.DeclaringType,
                info.Name,
                info.PropertyType,
                info.GetCustomAttributes(attrInherit).Cast<Attribute>().ToArray());
        }

    }
}
